package com.androsz.electricsleepbeta.widget; import java.io.IOException; import java.io.StreamCorruptedException; import java.util.List; import org.achartengine.GraphicalView; import org.achartengine.chart.AbstractChart; import org.achartengine.chart.TimeChart; import org.achartengine.model.PointD; import org.achartengine.model.XYMultipleSeriesDataset; import org.achartengine.renderer.XYMultipleSeriesRenderer; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.Cursor; import android.graphics.Color; import android.graphics.Paint.Align; import android.util.AttributeSet; import android.util.Log; import com.androsz.electricsleepbeta.R; import com.androsz.electricsleepbeta.app.SettingsActivity; import com.androsz.electricsleepbeta.db.SleepSession; import com.androsz.electricsleepbeta.util.MathUtils; public class SleepChart extends GraphicalView { private static final String TAG = SleepChart.class.getSimpleName(); /** The default number of X label ticks. */ private static final int DEFAULT_X_LABEL_TICKS = 5; final Context mContext; public int rating; private XYMultipleSeriesDataset mDataset; private XYMultipleSeriesRenderer mRenderer; private int DEFAULT_BOTTOM_MARGIN = 20; private int DEFAULT_LEFT_MARGIN = 25; private int DEFAULT_TOP_MARGIN = 20; int mBackgroundColor; int mTextColor; int mCalibrationBorderColor; int mCalibrationColor; int mGridColor; int mMovementBorderColor; int mMovementColor; int mXLabelTicks; boolean mSetBackgroundColor; boolean mSetGridColor; boolean mSetInScroll; boolean mSetTextColor; boolean mShowGrid; boolean mShowLabels; boolean mShowLegend; boolean mShowTitle = true; private String mAxisFormat; public final SleepChartData mData; final int mDefStyle; AttributeSet mAttrs; public SleepChart(final Context context) { this(context, null); } public SleepChart(final Context context, final AttributeSet attrs) { this(context, attrs, 0); } public SleepChart(final Context context, final AttributeSet attrs, int defStyle) { super(context, attrs); mContext = context; mAttrs = attrs; mDefStyle = defStyle; mData = new SleepChartData(mContext); // Now begin processing attributes final TypedArray array = mContext.obtainStyledAttributes(mAttrs, R.styleable.SleepChart, mDefStyle, 0); if (array.hasValue(R.styleable.SleepChart_android_background)) { mSetBackgroundColor = true; mBackgroundColor = array.getColor( R.styleable.SleepChart_android_background, R.color.background_dark); } if (array.hasValue(R.styleable.SleepChart_android_textColor)) { mSetTextColor = true; mTextColor = array .getColor(R.styleable.SleepChart_android_textColor, R.color.text_dark); } if (array.getBoolean(R.styleable.SleepChart_setScroll, false)) { mSetInScroll = true; } if (array.hasValue(R.styleable.SleepChart_gridAxisColor)) { mSetGridColor = true; mGridColor = array.getColor(R.styleable.SleepChart_gridAxisColor, R.color.sleepchart_axis); } mXLabelTicks = array.getInteger(R.styleable.SleepChart_xLabelTicks, DEFAULT_X_LABEL_TICKS); mMovementColor = array.getColor(R.styleable.SleepChart_movementColor, R.color.sleepchart_movement_light); mMovementBorderColor = array.getColor( R.styleable.SleepChart_movementBorderColor, R.color.sleepchart_movement_border_light); mCalibrationColor = array.getColor( R.styleable.SleepChart_calibrationColor, R.color.sleepchart_calibration_light); mCalibrationBorderColor = array.getColor( R.styleable.SleepChart_calibrationBorderColor, R.color.sleepchart_calibration_border_light); mShowGrid = array.getBoolean(R.styleable.SleepChart_showGrid, true); mShowLabels = array.getBoolean(R.styleable.SleepChart_showLabels, true); mShowLegend = array.getBoolean(R.styleable.SleepChart_showLegend, true); mShowTitle = array.getBoolean(R.styleable.SleepChart_showTitle, true); setupData(); } @Override protected AbstractChart buildChart() { Log.d(TAG, "Attempting to build chart."); if (mChart != null) { Log.w(TAG, "Attempt to build chart when chart already exists."); return mChart; } mDataset = new XYMultipleSeriesDataset(); mRenderer = new XYMultipleSeriesRenderer(); // Set initial framing for renderer. mRenderer.setYAxisMin(0); // Referencing SettingsActivity.MAX_ALARM_SENSITIVITY causes errors in ADT. if (isInEditMode()) { mRenderer.setYAxisMax(1.0f); } else { mRenderer.setYAxisMax(SettingsActivity.MAX_ALARM_SENSITIVITY); } mRenderer.setXAxisMin(System.currentTimeMillis()); mRenderer.setXAxisMax(System.currentTimeMillis()); mRenderer.setPanEnabled(false, false); mRenderer.setZoomEnabled(false, false); TimeChart timeChart = new TimeChart(mDataset, mRenderer); timeChart.setDateFormat("h:mm:ss"); mChart = timeChart; return mChart; } /** * Return the calibration level if one is available; otherwise return * INVALID_CALIBRATION. * * Callers of this method could call hasCalibrationLevel first to determine * if information surrounding calibration is available prior to invoking * this method. */ public float getCalibrationLevel() { return mData.getCalibrationLevel(); } public boolean hasTwoOrMorePoints() { return mData.hasTwoOrMorePoints(); } public void reconfigure() { if (hasTwoOrMorePoints()) { synchronized (mData) { final double firstX = mData.getLeftMostTime(); final double lastX = mData.getRightMostTime(); mData.setupCalibrationSpan(firstX, lastX); mRenderer.setXAxisMin(firstX); mRenderer.setXAxisMax(lastX); } setupChartAxisFormat(); } else { Log.w(TAG, "Asked to reconfigure but it did not make sense to display."); } } public void setCalibrationLevelAndRedraw(final float calibrationLevel) { mData.setCalibrationLevel(calibrationLevel); reconfigure(); repaint(); } public void setScroll(boolean scroll) { mSetInScroll = scroll; } public void sync(final Cursor cursor) throws StreamCorruptedException, IllegalArgumentException, IOException, ClassNotFoundException { Log.d(TAG, "Attempting to sync with cursor: " + cursor); this.sync(new SleepSession(cursor)); } public void sync(final Double x, final Double y) { Log.d(TAG, "Syncing by adding a point. " + x + ", " + y); mData.add(x, y); reconfigure(); repaint(); } public void sync(List<PointD> points) { Log.d(TAG, "Syncing by replacing the list of points, new length is " + points.size()); synchronized (mData) { clear(); for (PointD point : points) { mData.add(point.x, point.y); } } reconfigure(); repaint(); } public void sync(final SleepSession sleepRecord) { Log.d(TAG, "Attempting to sync with sleep record: " + sleepRecord); mData.set(com.androsz.electricsleepbeta.util.PointD .convertToNew(sleepRecord.getData())); // TODO this need to take into account timezone information. if (mShowTitle) { mRenderer.setChartTitle(sleepRecord.getTitle(mContext)); } // setCalibrationLevel currently reconfigures and repaints. // if that changes, we will need a reconfigure and repaint here too. setCalibrationLevelAndRedraw(sleepRecord.getCalibrationLevel()); } public void clear() { mData.clear(); repaint(); } /** * Set the chart's axis format based upon current duration of the data. */ private void setupChartAxisFormat() { mRenderer.setShowLabels(mShowLabels); final double duration; String axisFormat; duration = mData.getDuration(); final int MSEC_PER_MINUTE = 1000 * 60; final int MSEC_PER_HOUR = MSEC_PER_MINUTE * 60; if (duration > (15 * MSEC_PER_MINUTE)) { axisFormat = "h:mm"; } else { axisFormat = "h:mm:ss"; } if (!axisFormat.equals(mAxisFormat)) { mAxisFormat = axisFormat; try { ((TimeChart) mChart).setDateFormat(mAxisFormat); } catch (ClassCastException cce) { Log.w(TAG, "Could not set the SleepChart's axis format because the underlying chart is not a TimeChart."); } } } /** * Helper method that initializes charting after insertion of data. */ private void setupData() { synchronized (mData) { // remove all existing series for (int i = 0; i < mDataset.getSeriesCount(); i++) { mDataset.removeSeries(i); } for (int i = 0; i < mRenderer.getSeriesRendererCount(); i++) { mRenderer .removeSeriesRenderer(mRenderer.getSeriesRendererAt(i)); } // add series to the dataset and renderer mData.attachToDataset(mDataset); mData.attachToRenderer(mRenderer); } final float textSize = MathUtils.calculatePxFromSp(mContext, 14); mRenderer.setChartTitleTextSize(textSize); mRenderer.setAxisTitleTextSize(textSize); mRenderer.setLabelsTextSize(textSize); mRenderer.setAntialiasing(true); mRenderer.setFitLegend(true); mRenderer.setLegendTextSize(textSize); mRenderer.setXLabels(mXLabelTicks); mRenderer.setYLabels(8); mRenderer.setYLabelsAlign(Align.RIGHT); setupStyle(); // this ensures that labels are not drawn until we have data. mRenderer.setShowLabels(false); } /** * Iterate over the known attributes for this view setting our chart to the * desired settings. */ private void setupStyle() { // TODO remove comment // After this point buildChart() should have been invoked and the // various renders and series // should have been populated. if (mAttrs == null) { Log.d(TAG, "No attributes nothing to process."); return; } Log.d(TAG, "Processing attributes."); // background color processing if (mSetBackgroundColor) { mRenderer.setBackgroundColor(mBackgroundColor); mRenderer.setMarginsColor(mBackgroundColor); mRenderer.setApplyBackgroundColor(true); } else { mRenderer.setBackgroundColor(Color.TRANSPARENT); mRenderer.setMarginsColor(Color.TRANSPARENT); mRenderer.setApplyBackgroundColor(true); } if (mSetTextColor) { mRenderer.setLabelsColor(mTextColor); mRenderer.setAxesColor(mTextColor); } // SleepChart_setScroll mRenderer.setInScroll(mSetInScroll); // SleepChart_gridAxisColor if (mSetGridColor) { mRenderer.setGridColor(mGridColor); } else { mRenderer.setGridColor(R.color.sleepchart_axis); } mData.setSeriesColors(mMovementColor, mMovementBorderColor, mCalibrationColor, mCalibrationBorderColor); // SleepChart_showGrid mRenderer.setShowGrid(mShowGrid); Resources res = mContext.getResources(); int[] margins = mRenderer.getMargins(); mRenderer.setShowLabels(mShowLabels); if (mShowLabels) { try { margins[1] += res.getDimension(R.dimen.sleep_chart_left_margin); } catch (android.content.res.Resources.NotFoundException e) { margins[1] += DEFAULT_LEFT_MARGIN; // increase left margin } } mRenderer.setShowLegend(mShowLegend); if (mShowLegend) { try { margins[2] += res .getDimension(R.dimen.sleep_chart_bottom_margin); } catch (android.content.res.Resources.NotFoundException e) { margins[2] += DEFAULT_BOTTOM_MARGIN; // increase bottom margin } } if (mShowTitle) { try { margins[0] += res.getDimension(R.dimen.sleep_chart_top_margin); } catch (android.content.res.Resources.NotFoundException e) { margins[0] += DEFAULT_TOP_MARGIN; // increase top margin } } mRenderer.setMargins(margins); } }